package com.jungle.client.channel;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;
import javafx.application.Platform;
import lombok.extern.slf4j.Slf4j;
import okhttp3.*;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Type;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.stream.Collectors;

import static com.jungle.client.channel.TypeHelper.*;

@Slf4j
public class HttpClient {
    private final String baseUrl;

    private Request request;

    public HttpClient(String baseUrl) {
        this.baseUrl = baseUrl;
    }

    public static HttpClient getInstance() {
        return new HttpClient("http://localhost:8080");
    }

    private static final int CACHE_SIZE = 10 * 1024 * 1024;
    private static final java.io.File CACHE_DIR = new java.io.File("cache");
    private static final Gson gson = new GsonBuilder()
            //.excludeFieldsWithModifiers(Modifier.PROTECTED)
            //.excludeFieldsWithModifiers(Modifier.STATIC)
            //.addReflectionAccessFilter(ReflectionAccessFilter.BLOCK_INACCESSIBLE_JAVA)
            .create();
    public static final OkHttpClient client = new OkHttpClient.Builder()
            .addInterceptor(new RetryInterceptor(3))
            .connectTimeout(60, TimeUnit.SECONDS)
            .readTimeout(60, TimeUnit.SECONDS)
            .writeTimeout(60, TimeUnit.SECONDS)
            .cache(new Cache(CACHE_DIR, CACHE_SIZE))
            .build();

    public HttpClient get(String url, Map<String, Object> params) {
        String fullUrl = (params == null || params.isEmpty()) ? baseUrl.concat(url)
                : baseUrl.concat(url).concat("?").concat(toQueryString(params));

        request = getRequest(fullUrl);

        return this;
    }

    public HttpClient get(String url) {
        String fullUrl = baseUrl.concat(url);

        request = getRequest(fullUrl);

        return this;
    }

    private Request getRequest(String fullUrl) {
        Headers headers = getHeaders();
        return new Request.Builder().url(fullUrl).headers(headers).build();
    }

    private static Headers getHeaders() {
        return new Headers.Builder()
                .add("Content-Type", "application/json")
                .build();
    }


    private static String toQueryString(Map<String, Object> params) {
        return params.entrySet().stream()
                .filter(e -> e.getValue() != null)
                .map(e -> encode(e.getKey()) + "=" + encode(e.getValue().toString())
                )
                .collect(Collectors.joining("&"));
    }

    private static String encode(String key) {
        return URLEncoder.encode(key, StandardCharsets.UTF_8);
    }

    public HttpClient post(String url, String json) {
        RequestBody requestBody = RequestHelper.toRequestBody(json);
        request = postRequest(url, requestBody);
        return this;
    }

    private Request postRequest(String url, RequestBody requestBody) {
        return new Request.Builder()
                .url(baseUrl.concat(url))
                .headers(getHeaders())
                .post(requestBody)
                .build();
    }


    public HttpClient post(String url, Object param) {
        RequestBody requestBody = RequestHelper.toRequestBody(param);
        request = postRequest(url, requestBody);
        return this;
    }


    public HttpClient post(String url, File file) {
        RequestBody requestBody = RequestHelper.toRequestBody(file);
        request = postRequest(url, requestBody);
        return this;
    }

    public <T> T toResult(Class<T> clazz) throws IOException {
        return resp(client.newCall(request), clazz);
    }

    public <T> T toResult(TypeToken<T> clazz) throws IOException {
        return resp(client.newCall(request), clazz);
    }

    public <T> void callback(Class<T> clazz, Consumer<T> consumer) {
        resp(client.newCall(request), TypeToken.get(clazz), consumer);
    }

    public <T> void callback(TypeToken<T> typeToken, Consumer<T> consumer) {
        resp(client.newCall(request), typeToken, consumer);
    }

    /**
     * @param call  请求调用对象
     * @param clazz 响应结果对象类型
     */
    private static <T> T resp(Call call, Class<T> clazz) throws IOException {
        try (Response response = call.execute()) {
            if (response.isSuccessful()) {
                return toResult(call, clazz, response);
            } else {
                log.error("调用{}远程接口失败：{}", call.request().url().url(), response.message());
                return clazz.cast(emptyType(clazz));
            }
        }
    }

    private static <T> T resp(Call call, TypeToken<T> clazz) throws IOException {
        try (Response response = call.execute()) {
            if (response.isSuccessful()) {
                return toResult(call, clazz, response);
            } else {
                log.error("调用{}远程接口失败：{}", call.request().url().url(), response.message());
                return (T) emptyType(clazz.getType());
            }
        }
    }

    /**
     * TODO 处理HTTP响应，并返回指定类型的对象
     *
     * @param call     请求调用对象
     * @param clazz    响应结果类型
     * @param response 响应对象
     */
    private static <T> T toResult(Call call, Class<T> clazz, Response response) throws IOException {
        ResponseBody body = response.body();
        if (body == null) {
            return null;
        }
        if (clazz == String.class) {
            return clazz.cast(body.string());
        }
        Object wrappedObject = gson.fromJson(body.string(), wrappedType(clazz));
        if (wrappedObject instanceof JsonResult) {
            JsonResult<T> result1 = (JsonResult<T>) wrappedObject;
            if (result1.getCode() == 0) {
                //成功，返回结果
                return result1.getData();
            } else {
                HttpUrl url = call.request().url();
                //失败，输出失败信息
                log.error("调用{}远程接口异常：{}", url.url(), result1.getMessage());
                return clazz.cast(emptyType(clazz));
            }
        }
        return clazz.cast(wrappedObject);
    }

    private static <T> T toResult(Call call, TypeToken<T> typeToken, Response response) throws IOException {
        ResponseBody body = response.body();
        if (body == null) {
            return null;
        }

        Type wrappedType = wrappedType(com.google.common.reflect.TypeToken.of(typeToken.getType()));
        Object wrappedObject = gson.fromJson(body.string(), wrappedType);
        if (wrappedObject instanceof JsonResult) {
            JsonResult<T> result1 = (JsonResult<T>) wrappedObject;
            if (result1.getCode() == 0) {
                //成功，返回结果
                return result1.getData();
            } else {
                HttpUrl url = call.request().url();
                //失败，输出失败信息
                log.error("调用{}远程接口异常：{}", url.url(), result1.getMessage());
                return (T) emptyType(typeToken.getRawType());
            }
        }
        return (T) wrappedObject;
    }


    private static <T> void resp(Call call, Class<T> clazz, Consumer<T> consumer) {
        call.enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                log.error("调用{}远程接口失败：{}", call.request().url().url(), e.getMessage());
                consumer.accept(clazz.cast(emptyType(clazz)));
            }

            @Override
            public void onResponse(Call call, Response response) {
                Platform.runLater(() -> {
                    try {
                        T result = toResult(call, clazz, response);
                        consumer.accept(result);
                    } catch (IOException e) {
                        log.error("调用{}远程接口异常：{}", call.request().url().url(), e.getMessage());
                    }
                });
            }
        });
    }

    private static <T> void resp(Call call, TypeToken<T> typeToken, Consumer<T> consumer) {
        call.enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                log.error("调用{}远程接口失败：{}", call.request().url().url(), e.getMessage());
                Class<? super T> rawType = typeToken.getRawType();
                consumer.accept((T) emptyType(rawType));
            }

            @Override
            public void onResponse(Call call, Response response) {
                Platform.runLater(() -> {
                    try {
                        T result = toResult(call, typeToken, response);
                        consumer.accept(result);
                    } catch (IOException e) {
                        log.error("调用{}远程接口异常：{}", call.request().url().url(), e.getMessage());
                    }
                });
            }
        });
    }

}
